package com.example.springbootlucenedemo.common.lucene.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import com.example.springbootlucenedemo.common.lucene.IndexService;
import com.example.springbootlucenedemo.common.lucene.bean.EntityUtil;
import com.example.springbootlucenedemo.common.lucene.conf.LuceneConf;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.MMapDirectory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;

@Slf4j
@Service
@ConditionalOnProperty(name="spring.lucene.pub/subModel",havingValue="false",matchIfMissing = true)
public class IndexServiceImpl<V> implements IndexService<V> {
    @Autowired(required = false)
    private LuceneConf luceneConf;

    public IndexServiceImpl(LuceneConf luceneConf) {
        this.luceneConf = luceneConf;
    }
    public IndexServiceImpl( ) {
    }

    @Override
    public  void addDocument(Iterable<V> iterable ) {
            Iterator iterator = iterable.iterator();
            // Directory目录对象，目录对象表示索引库的位置
            Directory  directory =null;
            IndexWriter  writer =null;
            while (iterator.hasNext()){

                Object next = iterator.next();
                String luceneIndexPath = EntityUtil.getLuceneIndexPath(next);
                try {
                    if(directory == null ){
                        directory = new MMapDirectory(Paths.get(luceneConf.getPath()+ File.separator+luceneIndexPath));
                    }
                    // 创建分词器, 对英文分词支持很好，对中文是一个字分词
                    IndexWriterConfig indexWriterConfig = new IndexWriterConfig(luceneConf.getAnalyzer());
                    if(writer ==null){
                        writer = new IndexWriter(directory,  indexWriterConfig);
                    }
                    Document document = EntityUtil.parse(next);
                    log.info("== add index document: {}",document.toString());
                    Long l = writer.addDocument(document);
                } catch (LockObtainFailedException e){
                    try {
                        writer.close();
                        directory.close();
                        writer =  null;
                        directory = null;
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                    // 如果遇到写锁，等待锁的释放
                    log.info("\n ======wait====== \n");
                    try {
                        Thread.sleep(Math.abs(new Random(1000).nextInt()));
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    this.addDocument(iterable);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                writer.commit();
                writer.close();
                directory.syncMetaData();
                directory.close();
            } catch (NullPointerException e){
            }catch (IOException e) {
                e.printStackTrace();
            }finally {
                log.info("\n ======writer success ! "+ DateUtil.now() +"====== \n");
            }

    }
    @Override
    public Long addDocument(V object ) {
        String luceneIndexPath = EntityUtil.getLuceneIndexPath(object);
        IndexWriter writer = null;
        Directory  directory =null;
        try {
            // Directory目录对象，目录对象表示索引库的位置
            directory = new MMapDirectory(Paths.get(luceneConf.getPath()+ File.separator+luceneIndexPath));
            // 创建分词器, 对英文分词支持很好，对中文是一个字分词
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(luceneConf.getAnalyzer());
            writer = new IndexWriter(directory,  indexWriterConfig);
            Document document = EntityUtil.parse(object);
            log.info("== add index document: {}",document.toString());
            long l = writer.addDocument(document);
                writer.commit();
            return l;
        }catch (LockObtainFailedException e){
            try {
                writer.close();
                directory.close();
                writer =  null;
                directory = null;
            }catch (NullPointerException e1){

            } catch (IOException ex) {
                ex.printStackTrace();
            }
            // 如果遇到写锁，等待锁的释放
            log.info("\n ======wait====== \n");
            try {
                Thread.sleep(Math.abs(new Random(1000).nextInt()));
            } catch (InterruptedException ex) {
               // ex.printStackTrace();
            }
            this.addDocument(object);
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            log.info("\n ======writer success ! "+ DateUtil.now() +"====== \n");
            try {
                writer.commit();
                writer.close();
                directory.syncMetaData();
                directory.close();
            }catch (NullPointerException e){
                
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }


    /**
     * 组合查询条件
     * @param pageRequest (字段 搜索值 排序)
     * @return
     */ @Override
    public Page  search(com.example.springbootlucenedemo.common.lucene.bean.Page pageRequest){
        MultiReader multiReader =null;
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        pageRequest.getQuery().forEach((f,keyword)-> {
            try {
                builder.add(new QueryParser(f, luceneConf.getAnalyzer()).parse(keyword),BooleanClause.Occur.MUST);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        });
        Query query = builder.build();

        try {
            // 创建多个个IndexSearcher，以实现在多个索引目录进行查询
            List<IndexReader> allIndexReaderList = new ArrayList<>();
            for (File filePath : new File(luceneConf.getPath()).listFiles()) {
                if (filePath.isDirectory() && filePath.exists()) {
                    Directory indexDir = new MMapDirectory(filePath.toPath());
                    IndexReader indexReader = DirectoryReader.open(indexDir);
                    allIndexReaderList.add(indexReader);
                }
            }
            IndexReader[] fileIndexReaderArr = allIndexReaderList.toArray(new IndexReader[0]);
                multiReader = new MultiReader(fileIndexReaderArr, true);
            IndexSearcher indexSearcher = new IndexSearcher(multiReader);

            /*
            // 单索引文件夹查询
            // Directory目录对象，目录对象表示索引库的位置
            Directory  directory = new MMapDirectory(Paths.get(path+ File.separator+"tb_mission"));
            // 3. 创建索引读取对象
            IndexReader reader = DirectoryReader.open(directory);
            // 4. 创建索引搜索对象
            IndexSearcher indexSearcher = new IndexSearcher(reader);*/
            // 5. 执行搜索，获取结果集
            TopDocs result = null;
            Integer start = (pageRequest.getPageNumber()-1)* pageRequest.getSize();
                start = start == 0 ? pageRequest.getSize() : start;
            log.info("== search  document query: {}",query.toString());
            result = indexSearcher.search(query, Math.toIntExact(start),pageRequest.getSort());
            if(result.totalHits.value>start && pageRequest.getPageNumber()>1){
                start =Math.toIntExact(  result.totalHits.value < start ? result.totalHits.value:start)-1;
                ScoreDoc preScore = result.scoreDocs[start];
                log.info("== search  document query: {}",query.toString());
                result = indexSearcher.searchAfter(preScore, query, pageRequest.getSize(),pageRequest.getSort());
            }

            // 6. 解析结果集
            ScoreDoc[] scoreDocs = result.scoreDocs;
            List<Map<String,Object>> documentList = new LinkedList<>();
            for (ScoreDoc scoreDoc : scoreDocs) {
                int docId = scoreDoc.doc;
                Document doc = indexSearcher.doc(docId);
                List<IndexableField> fields = doc.getFields();
                Map<String,Object> map = new HashMap<>(fields.size());
                fields.forEach(field->map.put(field.name(),field.stringValue()));
                log.debug("== search  document result: {}",new JSONObject(map).toJSONString(0));
                documentList.add(map);
            }
            Page pageable = new PageImpl<Map<String,Object>>(documentList, PageRequest.of(pageRequest.getPageNumber(),pageRequest.getSize()), result.totalHits.value);
            return pageable;
        }catch(LockObtainFailedException e){
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
           return this.search(pageRequest);
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                multiReader.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return   new PageImpl<Map<String,String>>(new ArrayList<>(), PageRequest.of(pageRequest.getPageNumber(),pageRequest.getSize()), 0);
    }
    @Override
    public  List<Map<String,Object>>   search( int pageNumber,int size ,Sort sort,Query query){
        MultiReader multiReader =null;
        try {
            // 创建多个个IndexSearcher，以实现在多个索引目录进行查询
            List<IndexReader> allIndexReaderList = new ArrayList<>();
            for (File filePath : new File(luceneConf.getPath()).listFiles()) {
                if (filePath.isDirectory() && filePath.exists()) {
                    Directory indexDir = new MMapDirectory(filePath.toPath());
                    IndexReader indexReader = DirectoryReader.open(indexDir);
                    allIndexReaderList.add(indexReader);
                }
            }
            IndexReader[] fileIndexReaderArr = allIndexReaderList.toArray(new IndexReader[0]);
            multiReader = new MultiReader(fileIndexReaderArr, true);
            IndexSearcher indexSearcher = new IndexSearcher(multiReader);

            /*
            // 单索引文件夹查询
            // Directory目录对象，目录对象表示索引库的位置
            Directory  directory = new MMapDirectory(Paths.get(path+ File.separator+"tb_mission"));
            // 3. 创建索引读取对象
            IndexReader reader = DirectoryReader.open(directory);
            // 4. 创建索引搜索对象
            IndexSearcher indexSearcher = new IndexSearcher(reader);*/
            // 5. 执行搜索，获取结果集
            TopDocs result = null;
            Integer start = (pageNumber-1)* size;
            start = start == 0 ? size : start;
            log.info("== search  document query: {}",query.toString());
            result = indexSearcher.search(query, Math.toIntExact(start),sort);
            if(result.totalHits.value>start && pageNumber>1){
                start =Math.toIntExact(  result.totalHits.value < start ? result.totalHits.value:start)-1;
                ScoreDoc preScore = result.scoreDocs[start];
                log.info("== search  document query: {}",query.toString());
                result = indexSearcher.searchAfter(preScore, query, size,sort);
            }

            // 6. 解析结果集
            // ScoreDoc[] scoreDocs = result.scoreDocs;
            ScoreDoc[] scoreDocs = result.scoreDocs;
            List<Map<String,Object>> documentList = new LinkedList<>();
            for (ScoreDoc scoreDoc : scoreDocs) {
                int docId = scoreDoc.doc;
                Document doc = indexSearcher.doc(docId);
                List<IndexableField> fields = doc.getFields();
                Map<String,Object> map = new HashMap<>(fields.size());
                fields.forEach(field->map.put(field.name(),field.stringValue()));
                log.debug("== search  document result: {}",new JSONObject(map).toJSONString(0));
                documentList.add(map);
            }
            return documentList;
        }catch(LockObtainFailedException e){
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            return this. search( pageNumber,size ,sort,query);
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                multiReader.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return  null;
    }



    @Override
    public  Long deleteDocument(V obj){
        Directory  directory = null;
        IndexWriter writer = null;
        try {
            String luceneIndexPath = EntityUtil.getLuceneIndexPath(obj);
            // 单索引文件夹查询
            directory = new MMapDirectory(Paths.get(luceneConf.getPath()+ File.separator+luceneIndexPath));
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(luceneConf.getAnalyzer());
            writer = new IndexWriter(directory,  indexWriterConfig);
        // 5. 执行搜索，获取结果集
        TopDocs result = null;
        // 搜索条件
            BooleanQuery.Builder builder = new BooleanQuery.Builder();
                // 精确搜索
                builder.add(new TermQuery(new Term(EntityUtil.table, luceneIndexPath)),BooleanClause.Occur.MUST);
                EntityUtil.getQueryByPrimarykey(obj).forEach((query ->   builder.add(query,BooleanClause.Occur.MUST)));
                log.info("== delete index query: {}",builder.build().toString());
                long l = writer.deleteDocuments(builder.build());
            return l;
        }catch(LockObtainFailedException e){
            e.printStackTrace();
    }catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            writer.commit();
            writer.close();
            directory.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
        return null;
    }

    @Override
    public  Long updateDocument(V obj) {
        Directory  directory = null;
        IndexWriter writer = null;
        try {
            String getLuceneIndexPath = EntityUtil.getLuceneIndexPath(obj);
            // 单索引文件夹查询
            directory = new MMapDirectory(Paths.get(luceneConf.getPath()+ File.separator+getLuceneIndexPath));
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(luceneConf.getAnalyzer());
            writer = new IndexWriter(directory,  indexWriterConfig);
            // 5. 执行搜索，获取结果集
            TopDocs result = null;
            // 搜索条件
            BooleanQuery.Builder builder = new BooleanQuery.Builder();
            // 精确搜索
            builder.add(new TermQuery(new Term(EntityUtil.table, getLuceneIndexPath)),BooleanClause.Occur.MUST);
            List<Query> queryByPrimarykey = EntityUtil.getQueryByPrimarykey(obj);
            queryByPrimarykey.forEach((query)->builder.add(query,BooleanClause.Occur.MUST));
            log.info("== delete index query: {}",builder.build().toString());
            long l = writer.deleteDocuments(builder.build());
            Document document = EntityUtil.parse(obj);
            long l1 = writer.addDocument(document);
            log.info("== add index document: {}",document.toString());
            return l1;
        }catch(LockObtainFailedException e){
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                writer.commit();
                writer.close();
                directory.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
